WindowManagerService添加窗口测试

WindowManagerService创建窗口测试

通过WindowManagerService我们可以直接申请、渲染并注销自己的窗口,而不需要经过Activity,Dialog等系统组件,通过本测试,我们可以清楚WMS最基本的工作方式。以下代码基于Android4.4

创建一个SampleWindow类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
package test.wms.samplewindow;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.view.Choreographer;
import android.view.Display;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import android.os.ServiceManager;
import android.view.InputEventReceiver;
import android.view.InputChannel;
import android.view.DisplayInfo;
import android.view.WindowManagerGlobal;

import android.view.IWindow;
import android.view.IWindowSession;
import android.view.IWindowManager;
import android.hardware.display.IDisplayManager;

public class SampleWindow {

public static void main(String[] args) {
try{
//SampleWindow.Run()是这个程序的主入口
new SampleWindow().Run();
} catch (Exception e) {
e.printStackTrace();
}
}

//IWindowSession 是客户端向WMS请求窗口操作的中间代理,并且是进程唯一的
IWindowSession mSession = null;
//InputChannel 是窗口接收用户输入事件的管道。在第5章中将对其进行详细的探讨
InputChannel mInputChannel = new InputChannel();
// 下面的三个Rect保存了窗口的布局结果。其中mFrame表示了窗口在屏幕上的位置与尺寸
// 在4.4中将详细介绍它们的作用以及计算原理
Rect mInsets1 = new Rect();
Rect mInsets2 = new Rect();
Rect mFrame = new Rect();
Rect mVisibleInsets = new Rect();
Configuration mConfig = new Configuration();
//窗口的Surface,在此Surface上进行的绘制都将在此窗口上显示出来
Surface mSurface = new Surface();
// 用于在窗口上进行绘图的画刷
Paint mPaint = new Paint();
// 添加窗口所需的令牌
IBinder mToken = new Binder();
// 一个窗口对象,本例演示了如何将此窗口添加到WMS中,并在其上进行绘制操作
MyWindow mWindow = new MyWindow();
//WindowManager.LayoutParams定义了窗口的布局属性,包括位置、尺寸以及窗口类型等
WindowManager.LayoutParams mLp = new WindowManager.LayoutParams();
Choreographer mChoreographer = null;
//InputHandler 用于从InputChannel接收按键事件做出响应
InputHandler mInputHandler = null;
boolean mContinueAnime = true;

public void Run() throws Exception{
Looper.prepareMainLooper();
// 获取WMS服务
IWindowManager wms = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
// 通过WindowManagerGlobal获取进程唯一的IWindowSession实例。它将用于向WMS
// 发送请求。注意这个函数在较早的Android版本(如4.1)位于ViewRootImpl类中
mSession= WindowManagerGlobal.getWindowSession();

// 获取屏幕分辨率
IDisplayManager dm = IDisplayManager.Stub.asInterface(
ServiceManager.getService(Context.DISPLAY_SERVICE));

DisplayInfo di = dm.getDisplayInfo(Display.DEFAULT_DISPLAY);

Point scrnSize = new Point(di.appWidth, di.appHeight);
// 初始化WindowManager.LayoutParams
initLayoutParams(scrnSize);

// 将新窗口添加到WMS
installWindow(wms);

// 初始化Choreographer的实例,此实例为线程唯一。这个类的用法与Handler
// 类似,不过它总是在VSYC同步时回调,所以比Handler更适合做动画的循环器[1]
mChoreographer= Choreographer.getInstance();
// 开始处理第一帧的动画
scheduleNextFrame();
// 当前线程陷入消息循环,直到Looper.quit()
Looper.loop();
// 标记不要继续绘制动画帧
mContinueAnime= false;
// 卸载当前Window
uninstallWindow(wms);
}

public void initLayoutParams(Point screenSize) {
// 标记即将安装的窗口类型为SYSTEM_ALERT,这将使得窗口的ZOrder顺序比较靠前
mLp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
mLp.setTitle("SampleWindow");
// 设定窗口的左上角坐标以及高度和宽度
mLp.gravity = Gravity.LEFT | Gravity.TOP;
mLp.x = screenSize.x / 4;
mLp.y = screenSize.y / 4;
mLp.width = screenSize.x / 2;
mLp.height = screenSize.y / 2;
// 和输入事件相关的Flag,希望当输入事件发生在此窗口之外时,其他窗口也可以接受输入事件
mLp.flags = mLp.flags | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
}

public void installWindow(IWindowManager wms) throws Exception {
// 首先向WMS声明一个Token,任何一个Window都需要隶属与一个特定类型的Token
wms.addWindowToken(mToken,WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
// 设置窗口所隶属的Token
mLp.token = mToken;
// 通过IWindowSession将窗口安装进WMS,注意,此时仅仅是安装到WMS,本例的Window
// 目前仍然没有有效的Surface。不过,经过这个调用后,mInputChannel已经可以用来接受
// 输入事件了
mSession.add(mWindow,0, mLp, View.VISIBLE, mInsets2, mInputChannel);
/*通过IWindowSession要求WMS对本窗口进行重新布局,经过这个操作后,WMS将会为窗口
         创建一块用于绘制的Surface并保存在参数mSurface中。同时,这个Surface被WMS放置在
        LayoutParams所指定的位置上 */
mSession.relayout(mWindow,0, mLp, mLp.width, mLp.height, View.VISIBLE,
0, mFrame, mInsets1,mInsets2,mVisibleInsets, mConfig, mSurface);

if(!mSurface.isValid()) {
throw new RuntimeException("Failed creating Surface.");
}

// 基于WMS返回的InputChannel创建一个Handler,用于监听输入事件
//mInputHandler一旦被创建,就已经在监听输入事件了
mInputHandler= new InputHandler(mInputChannel, Looper.myLooper());
}

public void uninstallWindow(IWindowManager wms) throws Exception {
// 从WMS处卸载窗口
mSession.remove(mWindow);
// 从WMS处移除之前添加的Token
wms.removeWindowToken(mToken);
}

public void scheduleNextFrame() {
// 要求在显示系统刷新下一帧时回调mFrameRender,注意,只回调一次
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION
, mFrameRender, null);
}

// 这个Runnable对象用以在窗口上描绘一帧
public Runnable mFrameRender = new Runnable() {

@Override
public void run() {
try{
// 获取当期时间戳
long time = mChoreographer.getFrameTime() % 1000;
// 绘图
if (mSurface.isValid()) {
Canvas canvas = mSurface.lockCanvas(null);
canvas.drawColor(Color.DKGRAY);
canvas.drawRect(2 * mLp.width * time / 1000
- mLp.width, 0, 2 *mLp.width * time / 1000, mLp.height,mPaint);
mSurface.unlockCanvasAndPost(canvas);
mSession.finishDrawing(mWindow);
}
if(mContinueAnime){
scheduleNextFrame();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};

// 定义一个类继承InputEventReceiver,用以在其onInputEvent()函数中接收窗口的输入事件
class InputHandler extends InputEventReceiver {

Looper mLooper = null;

public InputHandler(InputChannel inputChannel, Looper looper) {
super(inputChannel,looper);
mLooper= looper;
}

@Override
public void onInputEvent(InputEvent event) {
if(event instanceof MotionEvent) {
MotionEvent me = (MotionEvent)event;
if (me.getAction() ==MotionEvent.ACTION_UP) {
// 退出程序
mLooper.quit();
}
}
super.onInputEvent(event);
}
}

// 实现一个继承自IWindow.Stub的类MyWindow
class MyWindow extends IWindow.Stub {

@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {

}
@Override
public void moved(int newX, int newY) {

}

@Override
public void dispatchAppVisibility(boolean visible) {

}

@Override
public void dispatchGetNewSurface() {

}

@Override
public void dispatchScreenState(boolean on) {

}

@Override
public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {

}

@Override
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {

}

@Override
public void closeSystemDialogs(String reason) {

}

@Override
public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync) {

}

@Override
public void dispatchDragEvent(DragEvent event) {
}

@Override
public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
int localValue, int localChanges) {

}

@Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {

}

@Override
public void doneAnimating() {

}
}
}

创建Android.mk文件

1
2
3
4
5
6
7
8
9
# Copyright 2012 The Android Open Source Project

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := samplewindow
include $(BUILD_JAVA_LIBRARY)

创建编译目录

将Android.mk和SampleWindow.java文件放在frameworks\base\cmds\samplewindow目录下,最终的结构如下:
frameworks\base\cmds\samplewindow\src\test\wms\samplewindow\SampleWindow.java
frameworks\base\cmds\samplewindow\Android.mk

编译模块

cd到Android源码目录中执行:

1
2
3
4
5
. /build/envsetup.sh
lunch xxx

cd frameworks\base\cmds\samplewindow\
mm

经过编译后的模块最终会在out\target\product\system目录下生成samplewindow.jar文件,将该文件拷贝出来

将jar文件转换为dex文件

由于jar文件不能再Android系统中直接运行,需要转换为dex文件,所以我们需要通过sdk工具dx来进行转换,dx脚本在
sdk/build-tools/目录下,执行dx --dex --output=samplewindow.dex samplewindow.jar生成dex文件

执行dex文件

通过app_process 命令来执行dex文件,命令如下

1
app_process -Djava.class.path=/data/local/tmp/samplewindow.dex /system/bin test.wms.samplewindow.SampleWindow

执行完成后手机屏幕就会显示我们创建的窗口,根据我们的设置,当在窗口触摸手指离开后退出窗口。

参考

深入理解WindowManagerService
https://blog.csdn.net/innost/article/details/47660193

坚持原创技术分享,您的支持将鼓励我继续创作!